package com.bsj.media.media;

import com.bsj.power.common.def.protocol.rtp.RtpParameterSet;
import com.bsj.power.common.util.Conv;
import lombok.extern.slf4j.Slf4j;

/**
 * 解析Fd数据，打包成flv数据
 *
 * @author chan
 */
@Slf4j
public class ParserVideoData {

    public static int getTypeFrame2(byte[] body) {
        // 数据长度长一步已经校验，不需要每步都校验
        int srcOffset = 0;
        // 通道ID，1字节
        srcOffset += 1;
        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据, 6: 表示GA下载当前文件发送完成
        return Byte.toUnsignedInt(body[srcOffset]);
    }

    /**
     * 判断当前是否是视频关键帧
     *
     * @param body
     * @return
     */
    public static boolean checkIframe(byte[] body) {
        // 数据长度长一步已经校验，不需要每步都校验, 数据体从第14字节开始
        int srcOffset = 0;
        // 通道ID，1字节
        srcOffset += 1;
        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据
        int dataType = Byte.toUnsignedInt(body[srcOffset++]);
        if (0 == dataType) {
            return true;
        }

        return false;
    }

    /**
     * @Title: checkIframe @Description: 判断当前是否是视频关键帧 @param @param b
     * 数据包 @param @param offset 数据包的第一字节 @param @param len
     * 数据包长度 @param @return 参数 @return boolean 返回类型 @throws
     */
    public static boolean checkIframe(byte[] b, int offset, int len) {
        if (b == null) {
            return false;
        }
        // 包头(2字节)+主信令(2字节)+终端号(6字节)+消息体长度(4字节)
        //offset += 14; （修改了协议）

        // 包头(2字节)+主信令(2字节)+终端号实际长度(1字节)+终端号(10字节)+消息体长度(4字节)
        offset += 19;
        // 通道ID，1字节
        offset += 1;
        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据
        int dataType = -1;
        if (offset + 1 <= len) {
            dataType = Byte.toUnsignedInt(b[offset++]);
        }

        if (0 == dataType) {
            return true;
        }

        return false;
    }

    /**
     * 判断当前帧是音频，还是视频
     *
     * @param body FD协议数据体
     * @return 0 音频数据，1 视频数据， -1 两者都不是
     */
    public static int getTypeFrame(byte[] body) {
        // 数据长度长一步已经校验，不需要每步都校验
        int srcOffset = 0;
        // 通道ID，1字节
        srcOffset += 1;
        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据, 6: 表示GA下载当前文件发送完成
        int dataType = Byte.toUnsignedInt(body[srcOffset]);
        if (0 == dataType || 1 == dataType || 2 == dataType) {
            return 1;
        } else if (3 == dataType) {
            return 0;
        }

        return -1;
    }

    /**
     * 获取当前音频类型，0 对应一帧音频大小是164字节， 1：320字节，2：160字节
     *
     * @param body FD协议解析后数据体
     * @return 返回音频类型枚举 0 ADPCM_IMA, 1 G711A, 2 G726_32K, 3 AAC
     */
    public static int getAudioType(byte[] body) {
        // 数据长度长一步已经校验，不需要每步都校验
        int srcOffset = 0;

        // 通道ID，1字节, 数据类型，1字节
        srcOffset += 2;

        // 编码格式，1字节
        // 音频时，描述音频编码格式 0：ADPCM 1：G711A 2：G726 3:AAC
        // 视频编码格式 0：mpeg 1：h264 2：mpjg 3：h265
        int encodingFormat = Byte.toUnsignedInt(body[srcOffset++]);

        return encodingFormat;
    }

    /**
     * 获取视频帧率
     *
     * @param body   FD协议传输数据体
     * @param offset 实际数据起始偏移位
     * @return 返回视频帧率
     */
    public static int getVideoFrame(byte[] body, int offset) {
        int rate = 0;

        int srcOffset = 4 + offset;
        // 码率类型[视频时为帧率，音频时为采样率]
        rate = Byte.toUnsignedInt(body[srcOffset]);

        return rate;
    }

    /**
     * 解析FdFd数据，打包成flv数据
     *
     * @param body           FdFd数据
     * @param buffer         打包后目的buffer
     * @param bufOffset      传入buffer可用开始偏移位置
     * @param isPacketHeader 是否有打包头
     * @return 返回打包成功后的数据长度， -10表示音频数据，
     */
    public static int PacketFlvData(byte[] body, byte[] buffer, int bufOffset, boolean isPacketHeader, int preTagSize) {
        // 当前buffer 数据偏移
        int dstOffset = bufOffset;

        // 数据长度长一步已经校验，不需要每步都校验
        int srcOffset = 0;

        // 通道ID，1字节
        int channel = body[srcOffset];
        srcOffset += 1;

        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据
        int dataType = Byte.toUnsignedInt(body[srcOffset++]);

        // 编码格式，1字节
        // 音频时，描述音频编码格式 0：ADPCM 1：G711A 2：G726
        // 视频编码格式 0：mpeg 1：h264 2：mpjg 3:h265
        @SuppressWarnings("unused")
        int encodingFormat = Byte.toUnsignedInt(body[srcOffset++]);

        // 音频时描述音频位率:0：8bit，1：16bit，2：32bit
        // 视频时分辨率如下：
        // 0-QCIF(176*144)
        // 1-CIF(352*288)
        // 2-2CIF(704*288)
        // 3-D1(704*576)
        // 4-720 (1280*720)
        // 5-960P(1280*960)
        // 6-1080P(1920*1080)
        // 100-127:自定义
        @SuppressWarnings("unused")
        int rateValue = Byte.toUnsignedInt(body[srcOffset++]); // 分辨率的值或者音频位率值

        // 码率类型[视频时为帧率，音频时为采样率]
        @SuppressWarnings("unused")
        int codeRateValue = Byte.toUnsignedInt(body[srcOffset++]);
        log.info("通道：" + channel + " 帧率： " + codeRateValue + " 编码格式:" + encodingFormat);

        // 音视频上报时间，单位毫秒(ms)
        long ltimeStamp = Conv.getLongNetOrder(body, srcOffset);
        int timeStamp = (int) ltimeStamp;
        srcOffset += 8;

        // 多媒体ID
        srcOffset += 8;

        // 数据体

        // 发送数据
        int mediaLen = 0;
        switch (dataType) {
            case 0: // 视频I帧
                if (!isPacketHeader) {
                    if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH264) {
                        log.info("打包h264 关键帧中的SPS，PPS信息");
                        // 打包h264 关键帧中的SPS，PPS信息
                        mediaLen = packetH264Header(body, srcOffset, buffer, dstOffset, preTagSize);
                    } else if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) {
                        log.info("打包h265 关键帧中的VPS,SPS和PPS信息");
                        mediaLen = packetH265Header(body, srcOffset, buffer, dstOffset, preTagSize);
                    }
                } else {
                    // 打包视频I帧
                    mediaLen = packetIVideoData(body, srcOffset, buffer, dstOffset, timeStamp, 15, preTagSize);
                }
                break;
            case 1:
            case 2:// 视频P，B帧
                if (!isPacketHeader) {
                    mediaLen = -1;
                } else {
                    // 打包视频B，P帧
                    mediaLen = packetBPVideoData(body, srcOffset, buffer, dstOffset, timeStamp, 15, preTagSize);
                }
                break;
            case 3: // 音频帧

            case 4: // 透传数据
                break;
            default:
                break;
        }

        return mediaLen;
    }

    public static int packetFlvData(byte[] body, byte[] buffer, int bufOffset, boolean isPacketHeader, int preTagSize,
                                    MediaSaveInfo mediaSaveInfo) {
        // 当前buffer 数据偏移
        int dstOffset = bufOffset;

        // 数据长度长一步已经校验，不需要每步都校验
        int srcOffset = 0;

        // 通道ID，1字节
        int channel = body[srcOffset];
        srcOffset += 1;

        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据
        int dataType = Byte.toUnsignedInt(body[srcOffset++]);

        // 编码格式，1字节
        // 音频时，描述音频编码格式 0：ADPCM 1：G711A 2：G726
        // 视频编码格式 0：mpeg 1：h264 2：mpjg 3:h265
        @SuppressWarnings("unused")
        int encodingFormat = Byte.toUnsignedInt(body[srcOffset++]);

        // 音频时描述音频位率:0：8bit，1：16bit，2：32bit
        // 视频时分辨率如下：
        // 0-QCIF(176*144)
        // 1-CIF(352*288)
        // 2-2CIF(704*288)
        // 3-D1(704*576)
        // 4-720 (1280*720)
        // 5-960P(1280*960)
        // 6-1080P(1920*1080)
        // 100-127:自定义
        @SuppressWarnings("unused")
        int rateValue = Byte.toUnsignedInt(body[srcOffset++]); // 分辨率的值或者音频位率值

        // 码率类型[视频时为帧率，音频时为采样率]
        @SuppressWarnings("unused")
        int codeRateValue = Byte.toUnsignedInt(body[srcOffset++]);
        log.info("通道：" + channel + " 帧率： " + codeRateValue + " 编码格式:" + encodingFormat);
        mediaSaveInfo.setChannel(channel);
        mediaSaveInfo.setCodeRateValue(codeRateValue);
        mediaSaveInfo.setEncodingFormat(encodingFormat);

        // 音视频上报时间，单位毫秒(ms)
        long ltimeStamp = Conv.getLongNetOrder(body, srcOffset);
        int timeStamp = (int) ltimeStamp;
        srcOffset += 8;

        // 多媒体ID
        srcOffset += 8;

        // 数据体

        // 发送数据
        int mediaLen = 0;
        switch (dataType) {
            case 0: // 视频I帧
                if (!isPacketHeader) {
                    if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH264) {
                        log.info("打包h264 关键帧中的SPS，PPS信息");
                        // 打包h264 关键帧中的SPS，PPS信息
                        mediaLen = packetH264Header(body, srcOffset, buffer, dstOffset, preTagSize);
                    } else if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) {
                        log.info("打包h265 关键帧中的VPS,SPS和PPS信息");
                        mediaLen = packetH265Header(body, srcOffset, buffer, dstOffset, preTagSize);
                    }
                } else {
                    // 打包视频I帧
                    mediaLen = packetIVideoData(body, srcOffset, buffer, dstOffset, timeStamp, 15, preTagSize);
                }
                break;
            case 1:
            case 2:// 视频P，B帧
                if (!isPacketHeader) {
                    mediaLen = -1;
                } else {
                    // 打包视频B，P帧
                    mediaLen = packetBPVideoData(body, srcOffset, buffer, dstOffset, timeStamp, 15, preTagSize);
                }
                break;
            case 3: // 音频帧

            case 4: // 透传数据
                break;
            default:
                break;
        }

        return mediaLen;
    }

    /**
     * 获取当前视频帧的分辨率类型
     *
     * @param body   FD协议解析数据体
     * @param offset 数据体偏移位
     * @return 返回分辨率枚举值
     */
    public static int getVideoSolution(byte[] body, int offset) {
        // 分辨率偏移位
        int soluOffset = offset + 3;

        return Byte.toUnsignedInt(body[soluOffset]);
    }

    /**
     * 封装FLV数据，第一次打包
     *
     * @param buffer          目的数据缓存区
     * @param bufOffset       目的数据写入偏移位置
     * @param timeStamp       时间戳
     * @param frameRate       帧率
     * @param flvType         MEDIA 1是音视频，VIDEO 2是视频，AUDIO 3是音频
     * @param duration        文件持续时间
     * @param filesize        文件大小
     * @param audioSampleRate 音频采样率
     * @param encodingFormat  编码格式： 视频编码格式0-mpeg，1-h264, 2-mpjg, 3-h265  音频编码格式 0-ADPCM,1-G711A,2-G726, 3-AAC
     * @return 返回打包成功的数据长度
     * @Param width
     * 视频宽
     * @Param height
     * 视频高
     */
    public static int packetFlvHeader(byte[] buffer, int bufOffset, int timeStamp, int frameRate, int flvType,
                                      int width, int height, int duration, int filesize, int audioSampleRate, int encodingFormat) {
        int mediaLen = 0;
        // 目的偏移位置
        int dstOffset = bufOffset;

        // flv header没有创建
        byte Flags = 0x01; // 0x04有音频，0x01有视频,0x05有音视频
        int videoCodecid = 0;
        int samplerate = 0;
        int samplebits = 0;
        int audioCodecid = 0;
        int nRate = 0; // 帧率
        if (flvType == 1) {
            Flags = 0x05;
            videoCodecid = (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) ? RtpParameterSet.flvCodecIdH265 : RtpParameterSet.flvCodecIdH264;
            nRate = frameRate;
            if (audioSampleRate == 0)
                samplerate = 44100;
            else
                samplerate = audioSampleRate;
            samplebits = 16;
            audioCodecid = 10;
        } else if (flvType == 2) {
            Flags = 0x01;
            nRate = frameRate;
            videoCodecid = (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) ? RtpParameterSet.flvCodecIdH265 : RtpParameterSet.flvCodecIdH264;
        } else {
            Flags = 0x04;
            if (audioSampleRate == 0)
                samplerate = 44100;
            else
                samplerate = audioSampleRate;
            samplebits = 16;
            audioCodecid = 10;
        }
        dstOffset = FlvPack.createFlvHeader(buffer, dstOffset, Flags);
        // 创建脚本，先创建脚本体
        int startPos = dstOffset;
        // FLV_CODECID_H264 = 7,
        dstOffset = FlvPack.setScriptBody(buffer, dstOffset + 15, nRate, width, height, videoCodecid, samplerate,
                samplebits, audioCodecid, duration, filesize);
        FlvPack.setBodyHeader(buffer, startPos, 0x12, 0, 0, dstOffset - startPos - 15);

        mediaLen = dstOffset - bufOffset;

        return mediaLen;
    }

    /**
     * 打包第一个音频帧
     *
     * @param dstBuffer  目的数据体
     * @param dstOffset  目的数据开始偏移位
     * @param preTagSize 前一个FLV Tag大小
     * @param audioType  音频类型 3表示aac； 0,1,2表示 adpcm g711a,g726
     * @return 返回目的地址，新的写入偏移位
     */
    public static int packetFirstAudioTag(byte[] dstBuffer, int dstOffset, int preTagSize, int audioType) {
        int mediaLen = 0;
        if (audioType == 3) {
            // 采样率16000.单声道
            mediaLen = packetAudioVideoData(null, 0, 0, dstBuffer, dstOffset, 0, true, preTagSize, 16000, 1);
        } else {
            // 采样率44100 ，单声道
            mediaLen = packetAudioVideoData(null, 0, 0, dstBuffer, dstOffset, 0, true, preTagSize, 44100, 1);
        }
        return mediaLen;
    }

    /**
     * 封装FLV数据，第一次打包
     *
     * @param body       源数据buffer
     * @param srcOffset  h264数据起始偏移位置
     * @param buffer     目的数据缓存区
     * @param bufOffset  目的数据写入偏移位置
     * @param preTagSize flv中的前一个tag大小
     * @return 成功返回打包的数据长度
     */
    public static int packetH264Header(byte[] body, int srcOffset, byte[] buffer, int bufOffset, int preTagSize) {

        int mediaLen = 0;
        int startPos = bufOffset;
        // h264 nalu帧
        NalInfo sps = new NalInfo();
        NalInfo pps = new NalInfo();

        // 读取一帧数据 sps pps
        int ret = ParserH264.ReadOneNaluFromBuf(body, body.length, srcOffset, sps);
        if (ret == ParserH264.RET_ERR_START_CODE_NOT_FOUND) {
            return -9;
        } else if (sps.getNal_unit_type() != 0x7) {
            return -7;
        }
        srcOffset = ret;
        ret = ParserH264.ReadOneNaluFromBuf(body, body.length, srcOffset, pps);
        if (ret < 0 || pps.getNal_unit_type() != 0x8) {
            // 走到这概率很小
            return -8;
        }
        srcOffset = ret;

        // 目的偏移位置
        int dstOffset = FlvPack.setFirstVideoHead(buffer, startPos + 15, sps.getBuf(), pps.getBuf(), RtpParameterSet.flvCodecIdH264);
        FlvPack.setBodyHeader(buffer, startPos, 0x9, 0, preTagSize, dstOffset - startPos - 15);

        mediaLen = dstOffset - bufOffset;

        return mediaLen;

    }

    /**
     * 封装FLV数据，第一次打包
     *
     * @param body       源数据buffer
     * @param srcOffset  h264数据起始偏移位置
     * @param buffer     目的数据缓存区
     * @param bufOffset  目的数据写入偏移位置
     * @param preTagSize flv中的前一个tag大小
     * @return 成功返回打包的数据长度
     */
    public static int packetH265Header(byte[] body, int srcOffset, byte[] buffer, int bufOffset, int preTagSize) {

        int mediaLen = 0;
        int startPos = bufOffset;
        // h265 nalu帧
        NalInfo vps = new NalInfo();
        NalInfo sps = new NalInfo();
        NalInfo pps = new NalInfo();

        // 读取一帧数据 vps sps pps
        int ret = ParserH265.ReadOneNaluFromBuf(body, body.length, srcOffset, vps);
        if (ret == ParserH265.RET_ERR_START_CODE_NOT_FOUND) {
            return -9;
        } else if (vps.getNal_unit_type() != RtpParameterSet.naluTypeVPSH5) {
            return -7;
        }
        srcOffset = ret;
        ret = ParserH265.ReadOneNaluFromBuf(body, body.length, srcOffset, sps);
        if (ret < 0 || sps.getNal_unit_type() != RtpParameterSet.naluTypeSPSH5) {
            // 走到这概率很小
            return -8;
        }
        srcOffset = ret;
        ret = ParserH265.ReadOneNaluFromBuf(body, body.length, srcOffset, pps);
        if (ret < 0 || pps.getNal_unit_type() != RtpParameterSet.naluTypePPSH5) {
            return -5;
        }
        srcOffset = ret;
        // 目的偏移位置
        int dstOffset = FlvPack.setFirstVideoHead(buffer, startPos + 15, vps.getBuf(), sps.getBuf(), pps.getBuf(), RtpParameterSet.flvCodecIdH265);
        FlvPack.setBodyHeader(buffer, startPos, 0x9, 0, preTagSize, dstOffset - startPos - 15);

        mediaLen = dstOffset - bufOffset;

        return mediaLen;

    }

    /**
     * 打包h264I帧数据为FLV
     *
     * @param srcData    数据源地址
     * @param srcOffset  数据源起始偏移位
     * @param dstBuffer  目的地址
     * @param bufOffset  目的地址起始偏移位
     * @param timeStamp  当前帧时间戳
     * @param frameRate  帧率
     * @param preTagSize 上一帧FLV tag 地址
     * @return 大于0返回打包的数据长度，-5找不到关键帧 -7 找不到sps -8找不到pps, -9 RTP I帧找不到h264头
     * 容错处理：设备传过来RTP I帧类型 可能是一些P帧
     */
    public static int packetIVideoData(byte[] srcData, int srcOffset, byte[] dstBuffer, int bufOffset, int timeStamp,
                                       int frameRate, int preTagSize) {
        int mediaLen = 0;

        // 目的偏移位置
        int dstOffset = bufOffset;
        // int preTagSize = 0;
        // h264 nalu帧
        NalInfo sps = new NalInfo();
        NalInfo pps = new NalInfo();
        NalInfo sei = new NalInfo();
        NalInfo nalu = new NalInfo();

        int startPos = dstOffset;
        // 读取一帧数据 sps pps
        int ret = ParserH264.ReadOneNaluFromBuf(srcData, srcData.length, srcOffset, sps);
        if (ret == -6) {
            return -9;
        } else if (sps.getNal_unit_type() != 0x7) {
            return -7;
        }
        srcOffset = ret;
        ret = ParserH264.ReadOneNaluFromBuf(srcData, srcData.length, srcOffset, pps);
        if (ret < 0 || pps.getNal_unit_type() != 0x8) {
            // 走到这概率很小
            return -8;
        }
        srcOffset = ret;

        // 读取I帧数据，如果是SEI则剔除
        ret = ParserH264.ReadOneNaluFromBuf(srcData, srcData.length, srcOffset, sei);

        int naluLen = 0; // nalu 长度
        if (ret != -6) {
            if (ret > 0 && sei.getNal_unit_type() == 6) {
                // 这是SEI帧数据，不用，取后面的65
                srcOffset = ret;
                ret = ParserH264.ReadOneNaluFromBuf(srcData, srcData.length, srcOffset, nalu);
                if (ret == -5) {
                    // 表示只有h264 帧头，00 00 00 01，这是正常数据
                    ret = srcOffset;
                    // 4 为start code长度
                    naluLen = srcData.length - srcOffset - 4;
                } else if (ret == -6) {
                    // 表示没有h264帧头，异常数据
                    log.warn("关键帧中SEI帧后面没有0x65数据 异常：" + srcData.length);
                    mediaLen = -5;
                } else if (ret > 0 && nalu.getNal_unit_type() == 5) {
                    // 也是异常数据，说明 I 帧后面还有其他数据
                    log.warn("关键帧数据后面还有其他帧 异常：" + srcData.length);
                    naluLen = ret - srcOffset - 4;
                } else if (ret > 0 && nalu.getNal_unit_type() != 5) {
                    //去掉关键帧中，其他的帧
                    while (ret > 0 && nalu.getNal_unit_type() != 5) {
                        srcOffset = ret;
                        ret = ParserH264.ReadOneNaluFromBuf(srcData, srcData.length, srcOffset, nalu);
                    }
                }
            } else if (ret == -5) {
                // 没有SEI帧
                naluLen = srcData.length - srcOffset - 4;
            }

            if (naluLen > 0) {
                // I 帧
                dstOffset = FlvPack.setVideoBody(dstBuffer, dstOffset + 15, srcData, srcOffset + 4, naluLen, 7, 1,
                        frameRate);
                FlvPack.setBodyHeader(dstBuffer, startPos, 0x9, timeStamp, preTagSize, dstOffset - startPos - 15);
                // 实际需要发送数据长度
                mediaLen = dstOffset - bufOffset; // 刚组装flv的tag长度
            }
        } else {
            mediaLen = -5;
        }

        return mediaLen;
    }

    /**
     * 打包h265I帧关键帧的数据为FLV
     *
     * @param srcData    数据源地址
     * @param srcOffset  数据源起始偏移位
     * @param dstBuffer  目的地址
     * @param bufOffset  目的地址起始偏移位
     * @param timeStamp  当前帧时间戳
     * @param frameRate  帧率
     * @param preTagSize 上一帧FLV tag 地址
     * @return 大于0返回打包的数据长度，-5找不到关键帧 -7 找不到sps -8找不到pps, -9 RTP I帧找不到h264头
     * 容错处理：设备传过来RTP I帧类型 可能是一些P帧
     */
    public static int packetH265IVideoData(byte[] srcData, int srcOffset, byte[] dstBuffer, int bufOffset, int timeStamp,
                                           int frameRate, int preTagSize) {
        int mediaLen = 0;
        // 目的偏移位置
        int dstOffset = bufOffset;
        int startPos = dstOffset;
        int ret = srcOffset;
        NalInfo nalu = new NalInfo();
        // 寻找关键帧
        do {
            srcOffset = ret + nalu.len; // 第一个nal头偏移量
            ret = ParserH265.realOneNalu(srcData, srcData.length, srcOffset, nalu); //第一个nal头偏移量
        } while (ret > 0 && nalu.getNal_unit_type() != RtpParameterSet.naluTypeIDRH5);

        if (ret > 0 && nalu.getNal_unit_type() == RtpParameterSet.naluTypeIDRH5) {
            // 查找关键帧
            dstOffset = FlvPack.setVideoBody(dstBuffer, dstOffset + 15, srcData, srcOffset + 4, nalu.len - 4, RtpParameterSet.flvCodecIdH265, 1,
                    frameRate);
            FlvPack.setBodyHeader(dstBuffer, startPos, 0x9, timeStamp, preTagSize, dstOffset - startPos - 15);
            mediaLen = dstOffset - bufOffset; // 刚组装flv的tag长度
        } else {
            log.info("未找到关键帧:" + ret);
            mediaLen = -6; // 异常长度
        }
        return mediaLen;
    }

    /**
     * 打包h264 BP帧数据为FLV
     *
     * @param srcData    数据源地址
     * @param srcOffset  数据源起始偏移位
     * @param dstBuffer  目的地址
     * @param bufOffset  目的地址起始偏移位
     * @param timeStamp  当前帧时间戳
     * @param frameRate  帧率
     * @param preTagSize 上一帧FLV tag 地址
     * @return 大于返回打包数据长度， -9 P帧找不到h264头。
     * 鉴于设备发来数据一包会有多个P帧，或者没有P帧，或者前面有乱数据后面有P帧情况，做不同情况处理
     */
    public static int packetBPVideoData(byte[] srcData, int srcOffset, byte[] dstBuffer, int bufOffset, int timeStamp,
                                        int frameRate, int preTagSize) {
        int mediaLen = 0;
        // 目的偏移位置
        int dstOffset = bufOffset;
        // 源buffer长度
        int srcLen = srcData.length - srcOffset;
        // codeid暂时只有H265和H264
        int codecid = srcData[2] == RtpParameterSet.fdVideoCodeTypeH265 ? RtpParameterSet.flvCodecIdH265 : RtpParameterSet.flvCodecIdH264;
        // 查找h264 start code
        int ret = ParserH264.FindStartCodeEx(srcData, srcOffset, srcData.length);
        // 前一tag帧大小

        if (ret >= 0) {
            while (true) {
                // 找到h264 开始头, 在找下是否有下一个h264 开始头
                int start0_code_start_size = (srcData[ret + 2] == 0x1) ? 3 : 4;
                // 计算剩余h264数据长度
                srcLen = srcData.length - ret - start0_code_start_size;
                int ret2 = ParserH264.FindStartCodeEx(srcData, ret + start0_code_start_size, srcData.length);
                if (ret2 == -1) {
                    // 说明就只有一P帧
                    int retOffset = FlvPack.setVideoBody(dstBuffer, dstOffset + 15, srcData,
                            ret + start0_code_start_size, srcLen, codecid, 2, frameRate);
                    FlvPack.setBodyHeader(dstBuffer, dstOffset, 0x9, timeStamp, preTagSize, retOffset - dstOffset - 15);
                    // 实际需要发送数据长度
                    mediaLen += (retOffset - dstOffset);
                    // 退出
                    break;
                } else if (ret2 > ret) {
                    // 说明后面还是帧, 先打包前面一帧
                    // 当前帧长度
                    srcLen = ret2 - ret - start0_code_start_size;
                    int retOffset = FlvPack.setVideoBody(dstBuffer, dstOffset + 15, srcData,
                            ret + start0_code_start_size, srcLen, codecid, 2, frameRate);
                    FlvPack.setBodyHeader(dstBuffer, dstOffset, 0x9, timeStamp, preTagSize, retOffset - dstOffset - 15);
                    // 实际需要发送数据长度
                    mediaLen += (retOffset - dstOffset);
                    // 设置下次查找h264 起始位置
                    ret = ret2;
                    // 当前帧大小
                    preTagSize = retOffset - dstOffset - 4;
                    dstOffset = retOffset;
                }
            }

        } else {
            // 找不到h264头，数据有问题
            mediaLen = -9;
        }

        return mediaLen;
    }

    /**
     * 解析打包FLV 音频帧
     *
     * @param srcData    源数据
     * @param srcOffset  源数据起始位
     * @param srcLen     源数据长度
     * @param dstBuffer  目的数据存储区
     * @param bufOffset  目的数据起始存储地址
     * @param timeStamp  时间戳
     * @param bFirst     是否第一个音频tag帧
     * @param samplerate 采样率
     * @param channel    声道数
     * @return 返回新的目的数据写入偏移位
     */
    public static int packetAudioVideoData(byte[] srcData, int srcOffset, int srcLen, byte[] dstBuffer, int bufOffset,
                                           int timeStamp, boolean bFirst, int preTagSize, int samplerate, int channel) {
        int mediaLen = 0;
        // 目的偏移位置
        int dstOffset = bufOffset;

        // 打包第一个音频tag帧
        // 声道数0-mono,1-stereo,采样精度0-8bit,1-16bit,采样率0-5.5K,1-11K,2-22K,3-44K
        // 数据格式 0-PCM,1-ADPCM,2-MP3,4-Nellymoser, 10-aac
        int aacSampleRate = 3; // 采样率44K
        int aacChannel = 1; // 立体声
        if (samplerate == 44100) {
            aacSampleRate = 3;
        } else if (samplerate == 16000) {
            aacSampleRate = 1;
        }
        if (channel == 2) {
            aacChannel = 1;
        } else if (channel == 1) {
            aacChannel = 0;
        }
        int retOffset = FlvPack.setAudioBody(dstBuffer, dstOffset + 15, srcData, srcOffset, srcLen, 10, aacSampleRate,
                1, aacChannel, bFirst);
        FlvPack.setBodyHeader(dstBuffer, dstOffset, 0x8, timeStamp, preTagSize, retOffset - dstOffset - 15);
        mediaLen = retOffset - dstOffset;

        return mediaLen;
    }

    /**
     * 获取ADTS封装中aac，原始数据长度
     *
     * @param data   原始音频数据
     * @param offset 数据起始偏移位
     * @param len    数据长度
     * @return 返回Adts原始数据帧长度
     */
    public static int getAdtsAACRawLen(byte[] data, int offset, int len) {
        int frameLen = 0;
        if (data[offset] == (byte) 0xFF && (data[offset + 1] & (byte) 0xF0) == (byte) 0xF0) {
            // ADTS头
            // adts 同步头为0xFFF 12位，
            // protection_absent：表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if
            // there is CRC, 头为9字节
            if ((data[1] & 0x01) == 0) {

            }

            int f_bit = data[offset + 3];
            int m_bit = data[offset + 4];
            int b_bit = data[offset + 5];

            frameLen += ((f_bit & 0x04) << 9);
            frameLen += (m_bit << 3);
            frameLen += ((b_bit & 0xE0) >> 5);
        }

        return frameLen;
    }

    /**
     * 获取ADTS封装中aac，头长度
     *
     * @param data   音频原始数据
     * @param offset 偏移位
     * @param len    数据长度
     * @return 返回ADTS帧，aac头长度
     */
    public static int getAdtsAACHeaderLen(byte[] data, int offset, int len) {
        int headerLen = 0;
        if (data[offset] == (byte) 0xFF && (data[offset + 1] & (byte) 0xF0) == (byte) 0xF0) {
            // ADTS头
            // adts 同步头为0xFFF 12位，
            // protection_absent：表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if
            // there is CRC, 头为9字节
            if ((data[1] & 0x01) == 0) {
                headerLen = 9;
            } else {
                headerLen = 7;
            }
        }

        return headerLen;
    }


}
